﻿using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using collision_and_rigid;
using game_bot;
using game_config;
using game_stuff;

namespace rogue_game
{
    [Serializable]
    public class RogueGame
    {
        public BotTeam BotTeam { get; }
        public Queue<int> ChapterIds { get; set; }
        public Chapter NowChapter { get; set; }
        public HashSet<CharacterInitData> CharacterInitDataS { get; set; }
        public Dictionary<int, RogueGamePlayer> GidToNowGamePlayers { get; set; }
        public int RebornCountDownTick { get; set; }

        public bool WaitForReborn { get; set; }
        public bool Pause { get; set; }
        public int ChapterCountDownTick { get; set; }
        public GameItem[] RebornCost { get; }
        public int PlayerLeaderGid { get; private set; }

        public PveMap NowPlayMap { get; private set; }

        private bool NeedCheckMapClear { get; set; }

        private bool OnReset { get; set; } = false;
        private Random Random { get; }

        public RogueGameSetting RogueGameSetting { get; set; }

        public bool GameOver { get; set; }

        public float NowChapterProcessingRate { get; set; }


        public int GameId { get; set; }

        public void EnSureAPlayerItemBag(int gid, GameItem[] nowInWallet)
        {
            GidToNowGamePlayers[gid].EnsureWalletItemInBag(nowInWallet);
        }


        public GameItem[] GetFinalBillInfo(int gid)
        {
            var checkItems = GidToNowGamePlayers[gid].FinalBill.CheckItems();
            return checkItems;
        }

        public void ChapterProcessingRate()
        {
            var configsChapterDepth = CommonConfig.Configs.chapter_depths[RogueGameSetting.Depth];
            var t = configsChapterDepth.Chapters.Length + (configsChapterDepth.ChapterToZip > 0 ? 1 : 0);
            var chapterIdsCount = ChapterIds.Count;
            NowChapterProcessingRate = (float)(t - chapterIdsCount) / t;
        }

        public static RogueGame GenByConfig(
            HashSet<CharacterInitData> characterBodies,
            int leaderId, RogueGameSetting rogueGameSetting, out ImmutableHashSet<IGameResp> outGameRespS)
        {
            var gameRespS = new HashSet<IGameResp>();
            var genByConfig = new RogueGame(characterBodies, leaderId, rogueGameSetting,
                out var ySlotArray);
            var pushChapterGoNext = new PushChapterGoNext(ySlotArray);
            gameRespS.Add(new GameStart(rogueGameSetting.Depth));
            gameRespS.Add(pushChapterGoNext);
            outGameRespS = gameRespS.ToImmutableHashSet();
            return genByConfig;
        }

        public ImmutableHashSet<IGameResp> GenLoadRespS()
        {
            var chapterIdsCount = ChapterIds.Count;
            return NowChapter.GenLoadChapterMsg(chapterIdsCount);
        }

        public void SetGameId(int id)
        {
            GameId = id;
        }

        public RogueGameSave Save()
        {
            var rogueGameSave = RogueGameSave.Save(this);
            return rogueGameSave;
        }

        //for load game
        public RogueGame(HashSet<CharacterInitData> characterBodies, int playerLeader, ChapterSave chapterSave,
            Dictionary<int, PlayerStatusSave> playerSaves, int restChapterNum, int nowMap,
            RogueGameSetting rogueGameSetting, int gameId, bool waitReborn)
        {
            CharacterInitDataS = characterBodies;
            RogueGameSetting = rogueGameSetting;
            ChapterIds = LoadChapters(RogueGameSetting.Depth, out _, out _, restChapterNum);
            Random = new Random();
            RebornCost = RogueLocalConfig.RogueRebornCost;
            var genByConfig = chapterSave.Load(Random, this);
            ChapterProcessingRate();
            NowChapter = genByConfig;
            GidToNowGamePlayers = playerSaves.ToDictionary(x => x.Key,
                x => new RogueGamePlayer(x.Value.LoadBody(x.Key), x.Value.LoadBill()));
            RebornCountDownTick = RogueLocalConfig.RogueRebornTick;
            WaitForReborn = waitReborn;
            ChapterCountDownTick = -1;
            PlayerLeaderGid = playerLeader;
            GameId = gameId;
            NowPlayMap = NowChapter.MGidToMap[nowMap];
            BotTeam = new BotTeam();
            NowPlayMap.AddCharacterBodiesToStart(GidToNowGamePlayers.Values.Select(x => x.Player));
            NowPlayMap.PlayGround.ActiveApplyDevice();
            NeedCheckMapClear = true;
            Pause = false;
            GameOver = false;
        }


        //for initGame
        private RogueGame(HashSet<CharacterInitData> characterBodies, int playerLeader,
            RogueGameSetting rogueGameSetting,
            out YSlot[][] ySlotArray)
        {
            CharacterInitDataS = characterBodies;
            RogueGameSetting = rogueGameSetting;
            var enumerable = CharacterInitDataS.Select(x => x.GenCharacterBody(TwoDPoint.Zero())).ToArray();
            ChapterIds = LoadChapters(RogueGameSetting.Depth, out var zipChapter, out var gameItem);
            Random = new Random();
            RebornCost = RogueLocalConfig.RogueRebornCost;

            if (ChapterIds == null) throw new Exception("no ChapterIds");
            var dequeue = ChapterIds.Dequeue();
            var (genByConfig, valueTuples) = Chapter.GenChapterById(dequeue, Random, this, dequeue == zipChapter);
            NowChapter = genByConfig;

            ySlotArray = valueTuples;
            GidToNowGamePlayers = enumerable.ToDictionary(x => x.GetId(), x => new RogueGamePlayer(x));

            foreach (var rogueGamePlayer in GidToNowGamePlayers.Values)
            {
                rogueGamePlayer.AddItem(gameItem);
            }

            RebornCountDownTick = RogueLocalConfig.RogueRebornTick;
            WaitForReborn = RogueLocalConfig.RogueRebornTick <= 0;

            ChapterCountDownTick = -1;
            PlayerLeaderGid = playerLeader;
            NowPlayMap = NowChapter.Entrance;
            BotTeam = new BotTeam();
            NowChapter.Entrance.AddCharacterBodiesToStart(enumerable);
            NeedCheckMapClear = true;
            Pause = false;
            GameOver = false;
        }

        private IGameResp ResetRogueGame(HashSet<CharacterInitData> resetGameNewCharacterStatusS,
            RogueGameSetting rogueGameSetting)
        {
            Pause = true;
            RogueGameSetting = rogueGameSetting;
            BotTeam.ClearBot();
            NowPlayMap.TelePortOut();
            ChapterIds = LoadChapters(RogueGameSetting.Depth, out var zipChapter, out var gameItem);
            if (resetGameNewCharacterStatusS.Any())
            {
                CharacterInitDataS = resetGameNewCharacterStatusS;
            }


            var dequeue = ChapterIds.Dequeue();
            var (genByConfig, valueTuples) = Chapter.GenChapterById(dequeue, Random, this, dequeue == zipChapter);
            NowChapter = genByConfig;
            var enumerable = CharacterInitDataS.Select(x => x.GenCharacterBody(TwoDPoint.Zero())).ToArray();
            GidToNowGamePlayers = enumerable.ToDictionary(x => x.GetId(), x => new RogueGamePlayer(x));
            foreach (var rogueGamePlayer in GidToNowGamePlayers.Values)
            {
                rogueGamePlayer.AddItem(gameItem);
            }

            RebornCountDownTick = RogueLocalConfig.RogueRebornTick;
            WaitForReborn = RogueLocalConfig.RogueRebornTick <= 0;
            ChapterCountDownTick = -1;
            NowPlayMap = NowChapter.Entrance;
            NowChapter.Entrance.AddCharacterBodiesToStart(enumerable);
            NeedCheckMapClear = true;
            var pushChapterGoNext = new PushChapterGoNext(valueTuples);

            Pause = false;
            OnReset = true;
            GameOver = false;
            return pushChapterGoNext;
        }


        private Queue<int> LoadChapters(int depth, out int zipChapter, out GameItem gameItem, int rest = -1)
        {
            var configsChapterDepth = CommonConfig.Configs.chapter_depths[depth];
            var intiCoin = configsChapterDepth.intiCoin;
            gameItem = new GameItem(item_id.coin, intiCoin);
            var configRogueChapters = configsChapterDepth.Chapters;
            zipChapter = configsChapterDepth.ChapterToZip;
            if (zipChapter > 0)
            {
                configRogueChapters = configRogueChapters.Reverse().Append(zipChapter).Reverse().ToArray();
            }

            var enumerable = configRogueChapters.Skip(configRogueChapters.Length - rest)
                .ToArray();
            var otherConfigRogueChapters = rest < 0 ? configRogueChapters : enumerable;

            return
                otherConfigRogueChapters
                    .Aggregate(new Queue<int>(), (ints, i) =>
                    {
                        ints.Enqueue(i);
                        return ints;
                    });
        }

        private bool Reborn(int seat, int toSeat)
        {
            if (!GidToNowGamePlayers.TryGetValue(seat, out var player)) return false;
            // var costOk = player.Player.CharacterStatus.PlayingItemBag.Cost(RebornCost);
            // if (!costOk) return false;
            // player.FinalBill.AddCost(RebornCost);

            if (GidToNowGamePlayers.TryGetValue(toSeat, out var player2))
            {
                player2.Player.ReBorn(player.Player.GetAnchor());
                player2.IsDead = false;
                WaitForReborn = false;
            }

            return true;
        }

        private bool LeaveGame(int gidSeat)
        {
            var leaveGame = GidToNowGamePlayers.Remove(gidSeat);
            if (leaveGame && gidSeat == PlayerLeaderGid && GidToNowGamePlayers.Any())
            {
                PlayerLeaderGid = GidToNowGamePlayers.First().Key;
            }

            return leaveGame;
        }

        private IGameResp GoNextChapter()
        {
            BotTeam.ClearBot();
            ChapterCountDownTick = -1;
            if (!ChapterIds.Any())
            {
                return new GameFinish(RogueGameSetting.Depth, GetItemsMap());
            }

            Pause = true;
            NowPlayMap.TelePortOut();
            var dequeue = ChapterIds.Dequeue();
            var (genByConfig, ySlotArray) = Chapter.GenChapterById(dequeue, Random, this,
                CommonConfig.Configs.chapter_depths[RogueGameSetting.Depth].ChapterToZip == dequeue);
            NowChapter = genByConfig;
            var nowChapterEntrance = NowChapter.Entrance;
            NowPlayMap = nowChapterEntrance;
            var characterBodies = GidToNowGamePlayers.Values.Select(x => x.Player).ToArray();
            NowPlayMap.AddCharacterBodiesToStart(characterBodies);
            var pushChapterGoNext = new PushChapterGoNext(ySlotArray);
            NeedCheckMapClear = true;
            Pause = false;

            return pushChapterGoNext;
        }

        private bool IsPlayerAllDead()
        {
            var all = GidToNowGamePlayers.Values.All(x => x.IsDead);
            return all;
        }


        private bool IsFail(out bool waitingReborn)
        {
            waitingReborn = false;
            var any = GidToNowGamePlayers.Any();
            if (!any)
            {
                return true;
            }

            if (IsPlayerAllDead())
            {
                waitingReborn = WaitForReborn && RebornCountDownTick >= 0;
                if (RebornCountDownTick >= 0)
                {
                    RebornCountDownTick -= 1;
                }


                return RebornCountDownTick < 0 && !WaitForReborn;
            }

            RebornCountDownTick = RogueLocalConfig.RogueRebornTick;
            return false;
        }


        private List<IGameResp> DoGameRequest(int callSeat, IGameRequest gameRequest)
        {
            var gameRespS = new List<IGameResp>();
            switch (gameRequest)
            {
                case ResetGame resetGame:
                    if (callSeat == PlayerLeaderGid)
                    {
                        // CheckOutItems(gameRespS);
                        var resetRogueGame =
                            ResetRogueGame(resetGame.NewCharacterStatusMap, resetGame.RogueGameSetting);
                        gameRespS.Add(resetRogueGame);
                        gameRespS.Add(new GameStart(RogueGameSetting.Depth));
                    }
                    else
                        gameRespS.Add(new QuestFail(callSeat));

                    break;
                case Pause _:
                    gameRespS.Add(PauseOrUnPauseGame(callSeat, gameRequest));
                    break;
                case GoNextChapter _:
                    if (ChapterCountDownTick > 0
#if DEBUG
||true
#endif
                       )
                    {
                        var goNextChapter = GoNextChapter();
                        gameRespS.Add(goNextChapter);
                    }
                    else
                        gameRespS.Add(new QuestFail(callSeat));

                    break;
                case KickPlayer kickPlayer:
                    if (callSeat == PlayerLeaderGid && LeaveGame(kickPlayer.Seat))
                        gameRespS.Add(new QuestOkResult(callSeat, kickPlayer));
                    else
                        gameRespS.Add(new QuestFail(callSeat));
                    break;
                case Leave leave:
                    if (LeaveGame(callSeat))
                        gameRespS.Add(new QuestOkResult(callSeat, leave));
                    else
                        gameRespS.Add(new QuestFail(callSeat));
                    break;
                case RebornPlayer rebornPlayer:
                    if (Reborn(callSeat, rebornPlayer.Seat))
                        gameRespS.Add(new QuestOkResult(callSeat, rebornPlayer));
                    else
                        gameRespS.Add(new QuestFail(callSeat));
                    break;

                case GiveUp _:
                    WaitForReborn = false;
                    break;
                default:
                    throw new ArgumentOutOfRangeException(nameof(gameRequest));
            }

            return gameRespS;
        }

        private void CheckOutItems(ICollection<IGameResp> gameRespS)
        {
            var gIdToItems = GameItemsMap();
            if (!gIdToItems.Any())
            {
                return;
            }

            gameRespS.Add(new OtherGameItemResult(gIdToItems));
            ClearItemBill();
        }

        public Dictionary<int, GameItem[]> GetItemsMap()
        {
            var gIdToItems = GameItemsMap();
            ClearItemBill();
            return gIdToItems;
        }

        private IGameResp PauseOrUnPauseGame(int callSeat, IGameRequest gameRequest)
        {
            Pause = !Pause;

            var questOkResult = new QuestOkResult(callSeat, gameRequest);
            return questOkResult;
        }


        // rogue游戏控制台包含核心玩法外的规则，应该与玩法异步运行
        public ImmutableHashSet<IGameResp> GameConsoleGoATick(Dictionary<int, IGameRequest> gameRequests)
        {
            var gameRespSet = new HashSet<IGameResp>();
            var enumerable = gameRequests
                .SelectMany(
                    x => DoGameRequest(x.Key, x.Value)).ToArray();
            if (enumerable.Any())
            {
                gameRespSet.UnionWith(enumerable);
            }

            if (ChapterCountDownTick > 0)
            {
                ChapterCountDownTick--;
                return gameRespSet.ToImmutableHashSet();
            }

            if (ChapterCountDownTick == 0)
            {
                ChapterCountDownTick--;
                var goNextChapter = GoNextChapter();
                if (goNextChapter is GameFinish && !GameOver)
                {
                    GameOver = true;
                }

                gameRespSet.Add(goNextChapter);
                return gameRespSet.ToImmutableHashSet();
            }

            if (NowChapter.IsPassAndNotReport())
            {
                NeedCheckMapClear = false;
                if (!ChapterIds.Any())
                {
                    BotTeam.ClearBot();

                    foreach (var gidToNowGamePlayer in GidToNowGamePlayers)
                    {
                        var gameItems = CommonConfig.OtherConfig!.RogueGameFinish
                            .Select(GameItem.GenByConfigGain).ToImmutableList();
                        gidToNowGamePlayer.Value.AddItem(gameItems);
                    }

                    var gameConsoleGoATick = new GameFinish(RogueGameSetting.Depth, GetItemsMap());

                    GameOver = true;
                    gameRespSet.Add(gameConsoleGoATick);

                    return gameRespSet.ToImmutableHashSet();
                }

                ChapterCountDownTick = RogueLocalConfig.ChapterPassTick;
                var chapterPass = new ChapterPass(NowChapter.ChapterId);
                gameRespSet.Add(chapterPass);
                return gameRespSet.ToImmutableHashSet();
            }
#if DEBUG
            // Console.Out.WriteLine($" clear!!~~~~~~~~~{NowPlayMap.IsClear}");
#endif
            if (NeedCheckMapClear && NowPlayMap.IsClear)
            {
                NowPlayMap.PlayGround.ActiveApplyDevice();
#if DEBUG
                // Console.Out.WriteLine(
                //     $" app!!~~~~~~~~~{NowPlayMap.PlayGround.GetMapApplyDevices().All(x => x.IsActive)}");
#endif
                NeedCheckMapClear = false;
                gameRespSet.Add(new GameMsgPush(GamePushMsg.MapClear));
            }

            if (!IsFail(out var waitingReborn) || GameOver)
            {
                if (waitingReborn)
                {
                    gameRespSet.Add(new WaitReborn());
                }

                return gameRespSet.ToImmutableHashSet();
            }

            {
                GameOver = true;
                gameRespSet.Add(new GameFail(GetItemsMap(), RogueGameSetting.Depth));
                return gameRespSet.ToImmutableHashSet();
            }
        }

        private void ClearItemBill()
        {
            foreach (var gidToNowGamePlayer in GidToNowGamePlayers)
            {
                gidToNowGamePlayer.Value.FinalBill.Clear();
            }
        }

        private Dictionary<int, GameItem[]> GameItemsMap()
        {
            var dictionary = GidToNowGamePlayers.ToDictionary(x => x.Key, x =>
                x.Value.CountItems());
            return dictionary;
        }

        // roguelike接入核心玩法，
        public RogueGameGoTickResult GamePlayGoATick(Dictionary<int, Operate> opDic)
        {
            if (Pause)
            {
                return RogueGameGoTickResult.Empty;
            }

            if (OnReset)
            {
                OnReset = false;
                return RogueGameGoTickResult.Empty2;
            }

            foreach (var botTeamTempOpThink in BotTeam.TempOperate.Where(botTeamTempOpThink =>
                         botTeamTempOpThink.Value != null))
            {
                opDic[botTeamTempOpThink.Key] = botTeamTempOpThink.Value!;
            }

            var playGroundGoTickResult = NowPlayMap.PlayGroundGoATick(opDic);
            BotTeam.AllBotsGoATick(playGroundGoTickResult);
            var characterBeHit = playGroundGoTickResult.CharacterGidBeHit;

            if (characterBeHit.Any())
            {
                var (all, kill, mapInteractableList) =
                    NowPlayMap.KillCreep(characterBeHit);

                foreach (var rogueGamePlayer in GidToNowGamePlayers)
                {
                    rogueGamePlayer.Value.AddItem(all);
                    if (kill.TryGetValue(rogueGamePlayer.Key, out var enumerable))
                    {
                        rogueGamePlayer.Value.AddItem(enumerable);
                    }

                    if (!rogueGamePlayer.Value.IsDead)
                    {
                        rogueGamePlayer.Value.CheckDead();
                    }
                }

                foreach (var mapInteractable in mapInteractableList)
                {
                    NowPlayMap.PlayGround.AddMapInteractable(mapInteractable);
                }
            }

            foreach (var gidToNowGamePlayer in GidToNowGamePlayers.Values)
            {
                gidToNowGamePlayer.GenInGameCostBills();
            }

            var playerTeleportTo = playGroundGoTickResult.ActOutPut;
            if (!playerTeleportTo.Any()) return new RogueGameGoTickResult(playGroundGoTickResult, false);
            var toOutPutResults = playerTeleportTo.Values.First();
            var telePortMsgS = toOutPutResults.OfType<TelePortMsg>().ToArray();
            if (!telePortMsgS.Any())
            {
                return new RogueGameGoTickResult(playGroundGoTickResult, false);
            }

            var (map, toPos) = telePortMsgS.First();
            if (NowPlayMap.PlayGround.MgId == map) return new RogueGameGoTickResult(playGroundGoTickResult, false);
#if DEBUG
            Console.Out.WriteLine($"map change ~~~~{map} ~~ {toPos}");
#endif


            NowPlayMap.TelePortOut();
            NowPlayMap = NowChapter.MGidToMap[map];
            NowPlayMap.TeleportToThisMap(GidToNowGamePlayers.Values.Select(x => (x.Player, toPos)));
            NowPlayMap.IsReached = true;
            NeedCheckMapClear = true;
            if (NowPlayMap.IsClear) return new RogueGameGoTickResult(playGroundGoTickResult, true);
            BotTeam.ClearBot();
            BotTeam.SetNaviMaps(NowPlayMap.PlayGround.ResMId);

            NowPlayMap.SpawnNpcWithBot(Random, BotTeam, NowChapter.ExtraPassiveNum);

            return new RogueGameGoTickResult(playGroundGoTickResult, true);
        }


        public void ForceSpawnNpc()
        {
            BotTeam.SetNaviMaps(NowPlayMap.PlayGround.ResMId);

            NowPlayMap.SpawnNpcWithBot(Random, BotTeam, NowChapter.ExtraPassiveNum);
        }

        public static RogueGame Empty()
        {
            return new RogueGame(new HashSet<CharacterInitData>(), 0, RogueGameSetting.Zero(), out _);
        }
    }

 


    public readonly struct RogueGameGoTickResult
    {
        public static RogueGameGoTickResult Empty = new RogueGameGoTickResult(PlayGroundGoTickResult.Empty, false);
        public static RogueGameGoTickResult Empty2 = new RogueGameGoTickResult(PlayGroundGoTickResult.Empty, true);

        public RogueGameGoTickResult(PlayGroundGoTickResult playGroundGoTickResult, bool mapChange)
        {
            PlayGroundGoTickResult = playGroundGoTickResult;
            MapChange = mapChange;
        }

        public PlayGroundGoTickResult PlayGroundGoTickResult { get; }

        public bool MapChange { get; }
    }

    public enum GameRequestType
    {
        KickPlayer,
        Leave,
#if DEBUG
        ForcePassChapter,
#endif
        RebornPlayer
    }
}